package com.Unidbg_hengdin.ndkdemo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.array.IntArray;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.linux.android.dvm.VarArg;

import com.github.unidbg.memory.Memory;
import com.github.unidbg.Module;
import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.commons.codec.binary.Hex;

import java.io.File;

public class Study07 extends  AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final Module dalvikModuleStudy08;
    private final DvmClass MainActivity;
    private final boolean logging;

    com.Unidbg_hengdin.ndkdemo.NNDKDemo nndkDemo;

    Study07(boolean logging) {
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.xiaojianbang.ndkdemo")
                .addBackendFactory(new Unicorn2Factory(true))
                .build();

        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM();
        vm.setJni(this);
        vm.setVerbose(logging);
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/Unidbg_hengdin/ndkdemo/libxiaojianbangA.so"), false);
        DalvikModule dalvikModuleB = vm.loadLibrary(new File("unidbg-android/src/test/java/com/Unidbg_hengdin/ndkdemo/libxiaojianbangB.so"), false);




        dalvikModule.callJNI_OnLoad(emulator);
//        dalvikModuleB.callJNI_OnLoad(emulator);
        module = dalvikModule.getModule();

        MainActivity = vm.resolveClass("com/xiaojianbang/ndkdemo/MainActivity");
        // 在主程序中加载 VirtualModule 的实现类对象
        Study08 myVirtualModule = new Study08(emulator, vm);
        dalvikModuleStudy08 = myVirtualModule.register(memory);


    }

    void destory() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }

    public static void main(String[] args) {
        Study07 study07 = new Study07(true);
        // 创建虚拟模块

        study07.callFun();
        study07.destory();
    }

    public void callFun() {
        StringObject res = MainActivity.callStaticJniMethodObject(emulator, "testJniFunc()Ljava/lang/String;");
        System.out.println(res.getValue());


        // 在主程序中调用 VirtualModule 的实现类对象中的函数
        Symbol encrypt_symbol = dalvikModuleStudy08.findSymbolByName("_encrypt");
        Number _Z7bcheckFunResInt =  encrypt_symbol.call(emulator,vm.getJNIEnv());
        System.out.println( _Z7bcheckFunResInt.toString());

    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        /***
         * callObjectMethodV 用于methodID 调用函数的接口 vaList 即为参数数组对象
         *  思路：
         *      - 通过 vaList 获取参数，并完成类型转换
         *      - 调用 模拟类的对应方法 (需要提前增加模拟类，并增加模拟类的对应参数，返回值的函数，即函数签名一致)
         *      - 调用该函数，转为dvm对象后，返回
         */
        if ("java/lang/Class->getClassLoader()Ljava/lang/ClassLoader;".equals(signature)) {
            /***
             * java.lang.UnsupportedOperationException: java/lang/Class->getClassLoader()Ljava/lang/ClassLoader;
             *      at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
             *  此处无参数，但是目前不太懂为何 newObject(null)
             */
            return vm.resolveClass("Ljava/lang/ClassLoader;").newObject(null);
        }
        /***
         * java.lang.UnsupportedOperationException: com/xiaojianbang/ndkdemo/NDKDemo->privateFunc(Ljava/lang/String;I)Ljava/lang/String;
         *     at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
         *     调用了 privateFunc 方法，该方法参数一为 string ,参数二为 int , 返回值为 string 数组
         */
        else if("com/xiaojianbang/ndkdemo/NDKDemo->privateFunc(Ljava/lang/String;I)Ljava/lang/String;".equals(signature)){
            StringObject args0 = vaList.getObjectArg(0);
            int args1 = vaList.getIntArg(1);

            String res = nndkDemo.privateFunc(args0.getValue(),args1);
            return new StringObject(vm,res);

        }

        return super.callObjectMethodV(vm, dvmObject, signature, vaList);

    }

    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        /***
         * allocObject 函数是用于创建Java对象的基础接口 ，不掉用构造函数，无需参数
         * 思路：
         *    - 新建一个模拟类的对象
         *    - 通过.newObject 将给定对象 转为 目标类的 对象
         */
        if ("com/xiaojianbang/ndkdemo/NDKDemo->allocObject".equals(signature)) {
            nndkDemo = new NNDKDemo("hengdin");
            return vm.resolveClass("com/xiaojianbang/ndkdemo/NDKDemo").newObject(nndkDemo);
        }


        return super.allocObject(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        /***
         * CallNonvirtualVoidMethod 调用的是这个方法
         *  思路：
         *  - 先通过 getObjectArg , getIntArg 获取到参数，并从dvm数据 转为 正常java 数据
         *  - 使用构建的模拟类， 补充对应参数签名的初始化方法
         *  - 创建一个模拟类的对象
         *  - 通过.newObject 将给定对象 转为 目标类的 对象
         */
        if ("com/xiaojianbang/ndkdemo/NDKDemo-><init>(Ljava/lang/String;I)V".equals(signature)) {
            StringObject args_str = vaList.getObjectArg(0);
            int num = vaList.getIntArg(1);
            nndkDemo = new NNDKDemo(args_str.getValue(), num);

            return vm.resolveClass("com/xiaojianbang/ndkdemo/NDKDemo").newObject(nndkDemo);
        }
        return super.newObjectV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        /***
         * getStaticObjectField 本身是通过 class.field 的方式去获取对象的属性，
         * 但在 jni 中，需要通过如下方式去获取属性,:
         *     1.获取FieldID  StaticFieldID = _JNIEnv::GetStaticFieldID(JNIEnv, NDKDemoClazz, "privateStaticStringField", "Ljava/lang/String;");
         *     2.获取Field    (const void *)_JNIEnv::GetStaticObjectField(JNIEnv, NDKDemoClazz, StaticFieldID);
         * 从而会显示报错为:
         *     java.lang.UnsupportedOperationException: com/xiaojianbang/ndkdemo/NDKDemo->privateStaticStringField:Ljava/lang/String;
         * 	    at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
         *  思路：
         *      - 需要获取类，因此需要创建一个类，该类需要有同样的属性，
         *      - 直接获取转换后java对象的目标属性
         *      - 将获取到的目标属性值包装为dvm 对象并返回
         */
        if ("com/xiaojianbang/ndkdemo/NDKDemo->privateStaticStringField:Ljava/lang/String;".equals(signature)) {
            // Class<?> _class = dvmClass.getValue(); 正常应该有这一步，但是没法实现，
            // 因此创建了 NNDKDemo类，作为傀儡，创建 对应的 static privateStaticStringField
            String _privateStaticStringField = NNDKDemo.privateStaticStringField;
            return new StringObject(vm, _privateStaticStringField);
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public void setObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature, DvmObject<?> value) {
        /***
         * setObjectField 本身是通过 obj.field = xxx 的方式去设置对象的目标属性，
         * 但在 jni 中，需要通过如下方式去获取属性 :
         *      1.获取 FieldID  FieldID = _JNIEnv::GetFieldID(JNIEnv, NDKDemoClazz, "privateStringField", "Ljava/lang/String;");
         *      2.设置 FieldID  _JNIEnv::SetObjectField(JNIEnv, NDKDemo_Obj, FieldID, v21);
         * 从而会显示报错为:
         *     java.lang.UnsupportedOperationException: com/xiaojianbang/ndkdemo/NDKDemo->privateStringField:Ljava/lang/String;
         *          at com.github.unidbg.linux.android.dvm.AbstractJni.setObjectField(AbstractJni.java:718)
         *  思路：
         *    - 目标对象从dvm 转为常规 java 类型
         *    - 将 目标属性值 转为常规 java类型
         *    - 对目表对象的属性进行赋值
         */
        if("com/xiaojianbang/ndkdemo/NDKDemo->privateStringField:Ljava/lang/String;".equals(signature)){
            System.out.println("setObjectField ---- " + value.getValue());
            NNDKDemo nndkDemoObj = (NNDKDemo)dvmObject.getValue();
            String fiedldValue = (String)value.getValue();
            nndkDemoObj.privateStringField = fiedldValue;
            return ;
        }
        super.setObjectField(vm, dvmObject, signature, value);
    }


    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        /***
         * getObjectField 本身是通过 obj.field 的方式去获取对象的属性，
         * 但在 jni 中，需要通过如下方式去获取属性,:
         *     1.获取FieldID  FieldID = _JNIEnv::GetFieldID(JNIEnv, NDKDemoClazz, "privateStringField", "Ljava/lang/String;");
         *     2.通过Field    ObjectField = _JNIEnv::GetObjectField(JNIEnv, NDKDemo_Obj, FieldID);
         * 从而会显示报错为:
         *     java.lang.UnsupportedOperationException: com/xiaojianbang/ndkdemo/NDKDemo->privateStringField:Ljava/lang/String;
         * 	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
         *  思路：
         *      - dvmObject 转为 普通java对象，
         *      - 直接获取转换后java对象的目标属性
         *      - 将获取到的目标属性值包装为dvm 对象并返回
         */
        if("com/xiaojianbang/ndkdemo/NDKDemo->privateStringField:Ljava/lang/String;".equals(signature)){
            // dvmObject 转为 普通java对象
            NNDKDemo nndkDemo1 = (NNDKDemo)dvmObject.getValue();
            // 直接调用转换后java对象的目标属性值
            String privateStringField = nndkDemo1.privateStringField;
            // 将获取到的目标属性值包装为dvm 对象并返回
            return new StringObject(vm,privateStringField);
        }else if ("com/xiaojianbang/ndkdemo/NDKDemo->byteArray:[B".equals(signature)){
            // dvmObject 转为 普通java对象
            NNDKDemo nndkDemo1 = (NNDKDemo)dvmObject.getValue();
            // 直接调用转换后java对象的目标属性值
            byte[] _byteArray = nndkDemo1.byteArray;
            // 将获取到的目标属性值包装为dvm 对象并返回
            return new ByteArray(vm, _byteArray);
        }
        return super.getObjectField(vm, dvmObject, signature);
    }

    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        /***
         * 创建的函数应该和要补的目标函数的属性尽量保持一致: static void
         *  在这里，NNDKDemo 的 publicStaticFunc 不为static 也可以，
         *  使用 NNDKDemo 对象调用也可以，但是尽量保持一致
         */
        if("com/xiaojianbang/ndkdemo/NDKDemo->publicStaticFunc()V".equals(signature)){
            NNDKDemo.publicStaticFunc();
            return ;
        }
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if("com/xiaojianbang/ndkdemo/NDKDemo->privateStaticFunc([Ljava/lang/String;)[I".equals(signature)){
            /***
             * 参数为数组，需要做两层处理，
             *      1.数组为dvm对象，通过getObjectArg 转为ArrayObject
             *      2 数组内的元素转为dvm对象，通过遍历元素转换，并添加到新的数组中
             *  最后将获得原始参数，并调用原函数
             */
            ArrayObject args0 = vaList.getObjectArg(0);
            DvmObject[] object_li = args0.getValue();

            String[] string_li = new String[object_li.length];

            for(int i=0; i<object_li.length; i++){
                string_li[i] = (String) object_li[i].getValue();
            }
            return  new IntArray(vm,NNDKDemo.privateStaticFunc(string_li));
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }


}